<!DOCTYPE html>
<html lang="en">
  <head>
    <title>p2.js Platformer example</title>
    <meta charset="utf-8">
    <script src="../../build/p2.js"></script>
  </head>
  <body>

    <!-- The canvas, where we draw stuff -->
    <canvas width="600" height="400" id="myCanvas"></canvas>

    <p>Use arrow keys to control character</p>

    <script>
      var canvas, ctx, w, h, zoom=50, jumpSpeed=6, walkSpeed=2, timeStep=1/60, maxSubSteps=10,
          world, characterBody, planeBody, platforms=[], boxes=[];

      var buttons = {
        space: 0,
        left: 0,
        right: 0
      };

      init();
      requestAnimationFrame(animate);

      function init(){

        // Init canvas
        canvas = document.getElementById("myCanvas");
        w = canvas.width;
        h = canvas.height;
        ctx = canvas.getContext("2d");
        ctx.lineWidth = 1/zoom;

        // Init world
        world = new p2.World();

        world.defaultContactMaterial.friction = 0.5;
        world.setGlobalStiffness(1e5);

        // Init materials
        var groundMaterial = new p2.Material(),
            characterMaterial = new p2.Material(),
            boxMaterial = new p2.Material();

        // Add a character body
        characterShape = new p2.Box({
          width: 0.5,
          height: 1,
          material: characterMaterial
        });
        characterBody = new p2.Body({
          mass: 1,
          position:[0,3],
          fixedRotation: true,
          damping: 0.5
        });
        characterBody.addShape(characterShape);
        world.addBody(characterBody);

        // Add a ground plane
        planeShape = new p2.Plane({
          material: groundMaterial
        });
        planeBody = new p2.Body({
          position:[0,-1]
        });
        planeBody.addShape(planeShape);
        world.addBody(planeBody);

        // Add platforms
        var platformPositions = [[2,0],[0,1],[-2,2]];
        for(var i=0; i<platformPositions.length; i++){
          var platformBody = new p2.Body({
            mass: 0, // Static
            position:platformPositions[i],
            type: p2.Body.KINEMATIC
          });
          var platformShape = new p2.Box({
            width: 1,
            height: 0.3,
            material: groundMaterial
          });
          platformBody.addShape(platformShape);
          world.addBody(platformBody);
          platforms.push(platformBody);
        }

        // Add movable boxes
        var boxPositions = [[2,1],[0,2],[-2,3]];
        for(var i=0; i<boxPositions.length; i++){
          var boxBody = new p2.Body({
            mass: 1,
            position:boxPositions[i]
          });
          var boxShape = new p2.Box({
            width: 0.8,
            height: 0.8,
            material: boxMaterial
          });
          boxBody.addShape(boxShape);
          world.addBody(boxBody);
          boxes.push(boxBody);
        }

        // Init contactmaterials
        var groundCharacterCM = new p2.ContactMaterial(groundMaterial, characterMaterial,{
          friction : 0, // No friction between character and ground
        });
        var boxCharacterCM = new p2.ContactMaterial(boxMaterial, characterMaterial,{
          friction : 0, // No friction between character and boxes
        });
        var boxGroundCM = new p2.ContactMaterial(boxMaterial, groundMaterial,{
          friction : 0.6, // Between boxes and ground
        });
        world.addContactMaterial(groundCharacterCM);
        world.addContactMaterial(boxCharacterCM);
        world.addContactMaterial(boxGroundCM);


        // Allow pass through platforms from below
        var passThroughBody, currentPlatform;

        world.on('beginContact', function (evt){
          if(evt.bodyA !== characterBody && evt.bodyB !== characterBody) return;
          var otherBody = evt.bodyA === characterBody ? evt.bodyB : evt.bodyA;
          var platformIndex = platforms.indexOf(otherBody);
          if(platformIndex != -1 && otherBody.position[1] > characterBody.position[1]){
            passThroughBody = otherBody;
          } else if(platformIndex != -1){
            currentPlatform = platforms[platformIndex];
          }
        });

        // Disable any equations between the current passthrough body and the character
        world.on('preSolve', function (evt){
          if(currentPlatform) characterBody.velocity[0] += currentPlatform.velocity[0];
          for(var i=0; i<evt.contactEquations.length; i++){
            var eq = evt.contactEquations[i];
            if((eq.bodyA === characterBody && eq.bodyB === passThroughBody) || eq.bodyB === characterBody && eq.bodyA === passThroughBody){
              eq.enabled = false;
            }
          }
          for(var i=0; i<evt.frictionEquations.length; i++){
            var eq = evt.frictionEquations[i];
            if((eq.bodyA === characterBody && eq.bodyB === passThroughBody) || eq.bodyB === characterBody && eq.bodyA === passThroughBody){
              eq.enabled = false;
            }
          }
        });

        world.on('endContact', function (evt){
          if((evt.bodyA === characterBody && evt.bodyB === passThroughBody) || evt.bodyB === characterBody && evt.bodyA === passThroughBody){
            passThroughBody = undefined;
          }
          if(evt.bodyA !== characterBody && evt.bodyB !== characterBody) return;
          var otherBody = evt.bodyA === characterBody ? evt.bodyB : evt.bodyA;
          var platformIndex = platforms.indexOf(otherBody);
          if(otherBody == currentPlatform){
            currentPlatform = null;
          }
        });

      }

      function drawBox(body){
        ctx.beginPath();
        var x = body.interpolatedPosition[0],
            y = body.interpolatedPosition[1],
            s = body.shapes[0];
        ctx.save();
        ctx.translate(x, y);     // Translate to the center of the box
        ctx.rotate(body.interpolatedAngle);  // Rotate to the box body frame
        ctx.fillRect(-s.width/2, -s.height/2, s.width, s.height);
        ctx.restore();
      }

      function drawPlane(){
        var y1 = planeBody.interpolatedPosition[1],
            y0 = -h/zoom/2,
            x0 = -w/zoom/2,
            x1 = w/zoom/2;
        ctx.fillRect(x0, y0, x1-x0, y1-y0);
      }

      function render(){
        // Clear the canvas
        ctx.clearRect(0,0,w,h);

        // Transform the canvas
        // Note that we need to flip the y axis since Canvas pixel coordinates
        // goes from top to bottom, while physics does the opposite.
        ctx.save();
        ctx.translate(w/2, h/2);  // Translate to the center
        ctx.scale(zoom, -zoom);   // Zoom in and flip y axis

        // Draw all bodies
        ctx.strokeStyle='none';

        ctx.fillStyle='green';
        drawPlane();
        for(var i=0; i<platforms.length; i++){
          drawBox(platforms[i]);
        }

        ctx.fillStyle='red';
        drawBox(characterBody);
        for(var i=0; i<boxes.length; i++){
          drawBox(boxes[i]);
        }

        // Restore transform
        ctx.restore();
      }

      world.on('postStep', function(){
        for(var i=0; i<platforms.length; i++){
          platforms[i].velocity[0] = 2*Math.sin(world.time);
        }

        // Apply button response
        characterBody.velocity[0] = walkSpeed * (buttons.right - buttons.left);
      });

      var lastTime;

      // Animation loop
      function animate(time){
        requestAnimationFrame(animate);

        var dt = lastTime ? (time - lastTime) / 1000 : 0;
        dt = Math.min(1 / 10, dt);

        // Move physics bodies forward in time
        world.step(timeStep, dt, maxSubSteps);

        // Render scene
        render();

        lastTime = time;
      }

      function checkIfCanJump(){
        for(var i=0; i<world.narrowphase.contactEquations.length; i++){
          var c = world.narrowphase.contactEquations[i];
          if(c.bodyA === characterBody || c.bodyB === characterBody){
            var d = c.normalA[1];
            if(c.bodyA === characterBody) d *= -1;
            if(d > 0.5) return true;
          }
        }
        return false;
      }

      window.onkeydown = function(event){
        switch(event.keyCode){
          case 38: // up
          case 32: // space
            if(!buttons.space){
              if(checkIfCanJump()) characterBody.velocity[1] = jumpSpeed;
              buttons.space = true;
            }
            break;
          case 39: buttons.right = 1; break;
          case 37: buttons.left = 1; break;
        }
      }

      window.onkeyup = function(event){
        switch(event.keyCode){
          case 38: // up
          case 32: buttons.space = 0; break;
          case 39: buttons.right = 0; break;
          case 37: buttons.left = 0; break;
        }
      }

    </script>

  </body>
</html>
